﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.Compilation;
using System.Web.UI;



/// <summary>
/// Custom ASP.NET Build Provider for the .jsx file extension.
/// </summary>
namespace AjaxControlFramework.Compilation
{
    [BuildProviderAppliesTo(BuildProviderAppliesTo.Web)]
    public class JavaScriptBuildProvider : BuildProvider
    {
        //------// Properties \\--------------------------------------------\\
        public virtual JsxParser Parser { get; set; }
        public virtual JavaScriptCodeDom CodeDom { get; protected set; }


        private string _fileName = null;
        public virtual string FileName
        {
            get
            {
                if (_fileName == null)
                {
                    if (String.IsNullOrEmpty(base.VirtualPath)) { return null; }

                    _fileName = VirtualPathUtility.GetFileName(base.VirtualPath);
                }

                return _fileName;
            }
        }


        public override CompilerType CodeCompilerType
        {
            get
            {
                string language = "VB"; // Default value.
                
                if (!Parser.ParsingComplete)
                {
                    using (TextReader reader = base.OpenReader())
                    {
                        CodeDom = Parser.ParseFile(reader, FileName);
                    }
                }

                if (Parser != null && !String.IsNullOrEmpty(Parser.Language))
                {
                    language = Parser.Language;
                }

                return base.GetDefaultCompilerTypeForLanguage(language);
            }
        }


        protected static Type VirtualPathType = Type.GetType("System.Web.VirtualPath, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);


        private object _virtualPathObj = null;
        protected object VirtualPathObj
        {
            get
            {
                if (_virtualPathObj == null)
                {
                    MethodInfo createMethodInfo = VirtualPathType.GetMethod("Create", (BindingFlags.Public | BindingFlags.Static), null, new Type[] { typeof(string) }, null);
                    _virtualPathObj = createMethodInfo.Invoke(null, new object[] { base.VirtualPath });
                }

                return _virtualPathObj;
            }
        }


        public delegate ExpressionBuilder GetExpressionBuilderMethod(string prefix, object virtualPath);

        private static GetExpressionBuilderMethod _getExpressionBuilder = null;
        protected static GetExpressionBuilderMethod GetExpressionBuilder 
        {
            get 
            {
                if (_getExpressionBuilder == null) 
                {
                    // Initialize and cache the dynamic method delegate to the ASP.NET internal static method "GetExpressionBuilder" of the System.Web.Compilation.ExpressionBuilder class.
                    MethodInfo getExpressionBuilderMethodInfo = typeof(ExpressionBuilder).GetMethod("GetExpressionBuilder", (BindingFlags.NonPublic | BindingFlags.Static), null, new System.Type[] { typeof(string), VirtualPathType }, null);

                    DynamicMethod dynamicMethod = new DynamicMethod("GetExpressionBuilder", typeof(ExpressionBuilder), new Type[] { typeof(string), typeof(object) }, true);
                    
                    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                    ilGenerator.Emit(OpCodes.Ldarg_0);
                    ilGenerator.Emit(OpCodes.Ldarg_1);
                    ilGenerator.Emit(OpCodes.Castclass, VirtualPathType);
                    ilGenerator.Emit(OpCodes.Call, getExpressionBuilderMethodInfo);
                    ilGenerator.Emit(OpCodes.Ret);

                    _getExpressionBuilder = ((GetExpressionBuilderMethod)(dynamicMethod.CreateDelegate(typeof(GetExpressionBuilderMethod))));
                }

                return _getExpressionBuilder;
            }
        }


        public delegate BoundPropertyEntry CreateBoundPropertyEntryMethod();

        private static CreateBoundPropertyEntryMethod _createBoundPropertyEntry = null;
        protected static CreateBoundPropertyEntryMethod CreateBoundPropertyEntry
        {
            get
            {
                if (_createBoundPropertyEntry == null)
                {
                    Type boundPropertyEntryType = typeof(BoundPropertyEntry);
                    ConstructorInfo createBoundPropertyConstructorInfo = boundPropertyEntryType.GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, Type.EmptyTypes, null);

                    DynamicMethod dynamicMethod = new DynamicMethod("CreateBoundProperty", boundPropertyEntryType, Type.EmptyTypes, true);
                    
                    ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                    ilGenerator.Emit(OpCodes.Newobj, createBoundPropertyConstructorInfo);
                    ilGenerator.Emit(OpCodes.Ret);

                    _createBoundPropertyEntry = (CreateBoundPropertyEntryMethod)dynamicMethod.CreateDelegate(typeof(CreateBoundPropertyEntryMethod));
                }

                return _createBoundPropertyEntry;
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Fields \\------------------------------------------------\\
        private CodeTypeDeclaration handlerClass = null;
        //------\\ Fields //------------------------------------------------//



        //------// Constructors \\------------------------------------------\\
        public JavaScriptBuildProvider()
            : base()
        {
            Parser = new JsxParser();
        }
        //------\\ Constructors //------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public override void GenerateCode(System.Web.Compilation.AssemblyBuilder assemblyBuilder)
        {
            TextWriter sourceCodeWriter = assemblyBuilder.CreateCodeFile(this);

            if (sourceCodeWriter != null)
            {
                using (sourceCodeWriter)
                {
                    // 1.) Generate the namespace of the dynamic class.
                    CodeNamespace codeNamespace = GenerateNamespace();

                    // 2.) Generate the class declaration.
                    CodeTypeDeclaration codeClass = GenerateClass();
                    codeNamespace.Types.Add(codeClass);

                    // 3.) Generate the "BuildScriptFile" method.
                    CodeMemberMethod buildScriptMethod = GenerateBuildScriptMethod(CodeDom);
                    codeClass.Members.Add(buildScriptMethod);


                    // 4.) 
                    ProcessRenderMode(CodeDom, CodeDom.ScriptType, out handlerClass);


                    // 5.) Generate the "FrameworkInitialize" method.

                    // Here we check to see if the inherited type exists and has been loaded into the current 
                    // AppDomain. BuildProviders are invoked after the initial compilation of an assembly, so 
                    // ASP.NET components that are not dynamically compiled at runtime (all components that do 
                    // not have a markup file with an associated code-behind file) will already have been 
                    // loaded into the AppDomain by now and will be found here. If the inherited type is NOT found
                    // then it is safe to assume that the all remaining uncompiled components are awaiting 
                    // processing by a BuildProvider. Only type names of classes that derive from 
                    // System.Web.UI.Control are allowed in the "Inherits" attribute of the <%@ Script %> 
                    // directive. Also, take note that the out-of-the-box BuildProviders of ASP.NET that build 
                    // Controls (.ascx and .aspx) work on System.Web.UI.TemplateControl types. The TemplateControl 
                    // class has a protected virtual method called "FrameworkInitialize" that is implemented by 
                    // one of the classes generated by the .ascx and .aspx BuildProviders. This generated class 
                    // derives from the actual control's class, rather than extend it as a partial of the 
                    // control's class. That means the generated class is a level down from the inheritance tree 
                    // of the .ascx/.aspx control's code-behind class. The dynamic class generated by the 
                    // JavaScriptBuildProvider is a partial of the control's class. It fills in the gap of 
                    // overrides of the "FrameworkInitialize" method by overriding and calling the base class's
                    // version. This leaves the class generated by the .ascx/.aspx BuildProvider to call the 
                    // "FrameworkInitialize" method of the class generated by the .jsx BuildProvider which will 
                    // call the "FrameworkInitialize" method of TemplateControl via the "base.FrameworkInitialize()" 
                    // statement.

                    // If the inherited type is found to exist and be loaded into the current AppDomain already, 
                    // the inheritence tree of that type is checked for a derivation from TemplateControl. If 
                    // the referenced type derives from TemplateControl then an override of the 
                    // "FrameworkInitialize" method can be generated.

                    // If the inherited type is found and does NOT derive from TemplateControl then we can assume 
                    // that a custom, non-template control (i.e. DropDownList or Panel) is being referenced. In 
                    // which case the "FrameworkInitialize" method does not exist anywhere in the inheritence tree 
                    // and an override for it should NOT be generated, thus avoiding a compilation error.

                    // This is all assuming that the type name referenced by the "Inherits" attribute in the 
                    // <%@ Script %> directive derives from the System.Web.UI.Control base class. If the type 
                    // does not derive from System.Web.UI.Control a compilation error will occur.

                    // The "FrameworkInitialize" method is invoked after instantiation of EVERY TemplateControl and 
                    // provides reliable place where code can be inserted that registers the .jsx script in the 
                    // JsxScriptRegistry. The JsxScriptRegistry is important for providing the 
                    // AjaxControl.InternalScriptManager a quick lookup for the script to see if a CompiledScript
                    // declaration is valid. This approach also provides a good place for a JsxRenderingHandler 
                    // delegate to be created that will perform the actual rendering of the .jsx content and store 
                    // the delegate in the JsxScriptRegistry.

                    Type inheritedType = System.Web.Compilation.BuildManager.GetType(CodeDom.InheritedTypeName, false);

                    if (inheritedType == null || (!String.IsNullOrEmpty(CodeDom.InheritedTypeName) && typeof(System.Web.UI.TemplateControl).IsAssignableFrom(inheritedType)))
                    {
                        CodeMemberMethod frameworkInitializeMethod = GenerateFrameworkInitializeMethod(CodeDom);
                        codeClass.Members.Add(frameworkInitializeMethod);
                    }




                    // ScriptType - The purpose of this attribute is to control in what form the compiled script is 
                    //              rendered to the page in. There are 3 options: File, Resource or Inline. The "File" 
                    //              option causes the compiled script to be rendered as if it were a normal external  
                    //              script file within a <script> block with its "src" attribute set. The value of 
                    //              the "src" attribute in this case is a psuedo file path that points to an HttpHandler  
                    //              that is compiled dynamically along with the script contents and is registered 
                    //              at runtime into the in-memory, object representation of the web application's 
                    //              web.config file. The "Resource" option causes the compiled script to be rendered 
                    //              as if it were an embedded resource within a <script> block where the file path set 
                    //              in the "src" attribute has the .axd file extension. With this option, the name of  
                    //              the embedded resource script cooresponds to the value provided in the "ScriptName" 
                    //              attribute of the <%@ Script %> directive. The "Inline" options causes the compiled 
                    //              script to be rendered as a normal <script> block whose content will be the rendered 
                    //              entirely within the <script> block's start and end tag, thus not being referenced 
                    //              externally with the "src" attribute.


                    CodeGeneratorOptions generatorOptions = new CodeGeneratorOptions();


                    if (inheritedType == null || (!String.IsNullOrEmpty(CodeDom.InheritedTypeName) && typeof(System.Web.UI.TemplateControl).IsAssignableFrom(inheritedType)))
                    {
                        //assemblyBuilder.AddAssemblyReference(BuildManager.GetCompiledAssembly(base.VirtualPath.Substring(0, base.VirtualPath.IndexOf(".jsx"))));

                        string templateControlVirtualPath = base.VirtualPath.Substring(0, base.VirtualPath.IndexOf(".jsx"));
                        //Assembly assembly = BuildManager.GetCompiledAssembly(templateControlVirtualPath);


                        MethodInfo addBuildProviderMethodInfo = assemblyBuilder.GetType().GetMethod("AddBuildProvider", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(BuildProvider) }, null);
                        MethodInfo createBuildProviderMethodInfo = typeof(BuildManager).GetMethod("CreateBuildProvider", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[] { VirtualPathType, typeof(System.Web.Configuration.CompilationSection), typeof(ICollection), typeof(bool) }, null);

                        System.Web.Configuration.CompilationSection compilationSection = System.Web.Configuration.WebConfigurationManager.GetWebApplicationSection("system.web/compilation") as System.Web.Configuration.CompilationSection;

                        MethodInfo createMethodInfo = VirtualPathType.GetMethod("Create", (BindingFlags.Public | BindingFlags.Static), null, new Type[] { typeof(string) }, null);
                        object virtualPath = createMethodInfo.Invoke(null, new object[] { templateControlVirtualPath });

                        BuildProvider desiredProvider = (BuildProvider)createBuildProviderMethodInfo.Invoke(null, new object[] { virtualPath, compilationSection, base.ReferencedAssemblies, true });


                        desiredProvider.GenerateCode(assemblyBuilder);
                        

                        /*CompilerParameters compilerParams = new CompilerParameters();
                        if (assembly != null)
                        {
                            compilerParams.CompilerOptions = "/moduleassemblyname:" + assembly.FullName;
                        }

                        CodeCompileUnit compileUnit = new CodeCompileUnit();
                        compileUnit.Namespaces.Add(codeNamespace);

                        assemblyBuilder.CodeDomProvider.CompileAssemblyFromDom(compilerParams, GenerateTypeForwardedAttribute(), compileUnit);*/
                    }
                    /*else
                    {
                        // Generate a CompiledJsxScriptAttribute assembly attribute for the generated Control partial type.
                        //assemblyBuilder.CodeDomProvider.GenerateCodeFromCompileUnit(GenerateCompiledJsxScriptAttribute(), sourceCodeWriter, generatorOptions);

                        // Generate the code for the constructed CodeNamespace object.
                        assemblyBuilder.CodeDomProvider.GenerateCodeFromNamespace(codeNamespace, sourceCodeWriter, generatorOptions);
                    }*/

                    assemblyBuilder.CodeDomProvider.GenerateCodeFromNamespace(codeNamespace, sourceCodeWriter, generatorOptions);

                    

                    if (handlerClass != null)
                    {
                        assemblyBuilder.CodeDomProvider.GenerateCodeFromMember(handlerClass, sourceCodeWriter, generatorOptions);
                        //RuntimeConfigUtils.AddHttpHandler(CodeDom.ScriptName + ".js", "GET", handlerClass.Name);
                    }
                }
            }
        }


        protected virtual CodeNamespace GenerateNamespace()
        {
            CodeNamespace namespaceSection = new CodeNamespace(DetermineReferencedTypeNamespace());

            namespaceSection.Imports.Add(new CodeNamespaceImport("System"));
            namespaceSection.Imports.Add(new CodeNamespaceImport("System.IO"));
            namespaceSection.Imports.Add(new CodeNamespaceImport("System.Text"));
            namespaceSection.Imports.Add(new CodeNamespaceImport("System.Web"));

            return namespaceSection;
        }


        protected virtual CodeTypeDeclaration GenerateClass()
        {
            CodeTypeDeclaration codeClass = new CodeTypeDeclaration(DetermineReferencedTypeName());

            codeClass.IsClass = true;
            codeClass.IsPartial = true;
            codeClass.TypeAttributes = TypeAttributes.Public;
            codeClass.BaseTypes.Add(new CodeTypeReference(typeof(IEmitsJsxScript)));

            return codeClass;
        }


        protected virtual CodeCompileUnit GenerateTypeForwardedAttribute()
        {
            CodeCompileUnit assemblyAttributeUnit = new CodeCompileUnit();

            CodeAttributeArgument[] attributeArguments = new CodeAttributeArgument[1];
            attributeArguments[0] = new CodeAttributeArgument("Destination", new CodePrimitiveExpression(CodeDom.InheritedTypeName));

            CodeAttributeDeclaration customAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.Runtime.CompilerServices.TypeForwardedToAttribute)), attributeArguments);

            assemblyAttributeUnit.AssemblyCustomAttributes.Add(customAttribute);
            assemblyAttributeUnit.ReferencedAssemblies.Add("System.dll");
            assemblyAttributeUnit.ReferencedAssemblies.Add("System.Web.dll");
            assemblyAttributeUnit.ReferencedAssemblies.Add("AjaxControl.dll");

            //CodeAttributeArgumentCollection attributeArguments = new CodeAttributeArgumentCollection();
            //attributeArguments.Add(new CodeAttributeArgument("FileName", new CodeSnippetExpression("\"" + FileName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("Inherits", new CodeSnippetExpression("\"" + CodeDom.InheritedTypeName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("ScriptName", new CodeSnippetExpression("\"" + CodeDom.ScriptName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("ScriptType", new CodeSnippetExpression("AjaxControl.ScriptType." + CodeDom.ScriptType.ToString())));
            //attributeArguments.Add(new CodeAttributeArgument("Singleton", new CodeSnippetExpression(CodeDom.Singleton ? "true" : "false")));
            //attributeArguments.Add(new CodeAttributeArgument("Language", new CodeSnippetExpression("\"" + CodeDom.Language + "\"")));

            return assemblyAttributeUnit;
        }


        /*
        protected virtual CodeCompileUnit GenerateCompiledJsxScriptAttribute()
        {
            CodeCompileUnit assemblyAttributeUnit = new CodeCompileUnit();

            CodeAttributeArgument[] attributeArguments = new CodeAttributeArgument[6];
            attributeArguments[0] = new CodeAttributeArgument("FileName", new CodePrimitiveExpression(FileName));
            attributeArguments[1] = new CodeAttributeArgument("Inherits", new CodePrimitiveExpression(CodeDom.InheritedTypeName));
            attributeArguments[2] = new CodeAttributeArgument("ScriptName", new CodePrimitiveExpression(CodeDom.ScriptName));
            attributeArguments[3] = new CodeAttributeArgument("ScriptType", new CodeSnippetExpression("AjaxControl.ScriptType." + CodeDom.ScriptType.ToString()));
            attributeArguments[4] = new CodeAttributeArgument("Singleton", new CodePrimitiveExpression(CodeDom.Singleton));
            attributeArguments[5] = new CodeAttributeArgument("Language", new CodePrimitiveExpression(CodeDom.Language));

            CodeAttributeDeclaration customAttribute = new CodeAttributeDeclaration(new CodeTypeReference(typeof(CompiledJsxScriptAttribute)), attributeArguments);

            assemblyAttributeUnit.AssemblyCustomAttributes.Add(customAttribute);
            assemblyAttributeUnit.ReferencedAssemblies.Add("System.dll");
            assemblyAttributeUnit.ReferencedAssemblies.Add("System.Web.dll");
            assemblyAttributeUnit.ReferencedAssemblies.Add("AjaxControl.dll");

            //CodeAttributeArgumentCollection attributeArguments = new CodeAttributeArgumentCollection();
            //attributeArguments.Add(new CodeAttributeArgument("FileName", new CodeSnippetExpression("\"" + FileName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("Inherits", new CodeSnippetExpression("\"" + CodeDom.InheritedTypeName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("ScriptName", new CodeSnippetExpression("\"" + CodeDom.ScriptName + "\"")));
            //attributeArguments.Add(new CodeAttributeArgument("ScriptType", new CodeSnippetExpression("AjaxControl.ScriptType." + CodeDom.ScriptType.ToString())));
            //attributeArguments.Add(new CodeAttributeArgument("Singleton", new CodeSnippetExpression(CodeDom.Singleton ? "true" : "false")));
            //attributeArguments.Add(new CodeAttributeArgument("Language", new CodeSnippetExpression("\"" + CodeDom.Language + "\"")));

            return assemblyAttributeUnit;
        }
        */


        protected virtual CodeMemberMethod GenerateFrameworkInitializeMethod(JavaScriptCodeDom javascriptCodeDom)
        {
            // 1.) Create the CodeDOM method.
            CodeMemberMethod method = new CodeMemberMethod();

            method.Name = "FrameworkInitialize";
            method.Attributes = MemberAttributes.Override | MemberAttributes.Family; // protected override
            method.ReturnType = new CodeTypeReference(typeof(void));


            // 2.) Create a CodeDOM reference to all of the object types to be used.
            CodeTypeReference templateControlType = new CodeTypeReference(typeof(System.Web.UI.TemplateControl));
            CodeTypeReference jsxScriptType = new CodeTypeReference(typeof(JsxScript));
            CodeTypeReference jsxScriptRegistryType = new CodeTypeReference(typeof(JsxScriptRegistry));


            // 3.) Create an instance of the JsxScript object.
            CodeVariableDeclarationStatement newJsxScriptObject = CreateObjectVariable(jsxScriptType, "jsxScript");
            method.Statements.Add(newJsxScriptObject);
            
            CodeVariableReferenceExpression jsxScript = new CodeVariableReferenceExpression("jsxScript");


            // 4.) Set all of the properties of the JsxScript instance.
            CodePropertyReferenceExpression fileNameProperty = new CodePropertyReferenceExpression(jsxScript, "FileName");
            method.Statements.Add(new CodeAssignStatement(fileNameProperty, new CodePrimitiveExpression(FileName)));

            CodePropertyReferenceExpression inheritsProperty = new CodePropertyReferenceExpression(jsxScript, "Inherits");
            method.Statements.Add(new CodeAssignStatement(inheritsProperty, new CodePrimitiveExpression(javascriptCodeDom.InheritedTypeName)));

            CodePropertyReferenceExpression scriptNameProperty = new CodePropertyReferenceExpression(jsxScript, "ScriptName");
            method.Statements.Add(new CodeAssignStatement(scriptNameProperty, new CodePrimitiveExpression(javascriptCodeDom.ScriptName)));

            CodePropertyReferenceExpression scriptTypeProperty = new CodePropertyReferenceExpression(jsxScript, "ScriptType");
            method.Statements.Add(new CodeAssignStatement(scriptTypeProperty, new CodeSnippetExpression("AjaxControl.ScriptType." + javascriptCodeDom.ScriptType.ToString())));

            CodePropertyReferenceExpression singletonProperty = new CodePropertyReferenceExpression(jsxScript, "Singleton");
            method.Statements.Add(new CodeAssignStatement(singletonProperty, new CodePrimitiveExpression(javascriptCodeDom.Singleton)));

            CodePropertyReferenceExpression languageProperty = new CodePropertyReferenceExpression(jsxScript, "Language");
            method.Statements.Add(new CodeAssignStatement(languageProperty, new CodePrimitiveExpression(javascriptCodeDom.Language)));

            if (handlerClass != null)
            {
                string handlerClassFullTypeName = DetermineReferencedTypeNamespace() + "." + handlerClass.Name;

                CodePropertyReferenceExpression handlerTypeProperty = new CodePropertyReferenceExpression(jsxScript, "HandlerType");
                method.Statements.Add(new CodeAssignStatement(handlerTypeProperty, new CodeMethodInvokeExpression(new CodeTypeReferenceExpression("System.Web.Compilation.BuildManager"), "GetType", new CodePrimitiveExpression(handlerClassFullTypeName), new CodePrimitiveExpression(false))));
            }

            CodePropertyReferenceExpression instanceProperty = new CodePropertyReferenceExpression(jsxScript, "Instance");
            method.Statements.Add(new CodeAssignStatement(instanceProperty, new CodeThisReferenceExpression()));


            // 5.) Create method reference to the "Add" static method of the JsxScriptRegistry class.
            CodeMethodInvokeExpression containsMethod = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(JsxScriptRegistry)), "Contains", jsxScript);

            CodeBinaryOperatorExpression containsScript = new CodeBinaryOperatorExpression(containsMethod, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(false));

            CodeConditionStatement ifNotContains = new CodeConditionStatement();
            ifNotContains.Condition = containsScript;
            ifNotContains.TrueStatements.Add(new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(JsxScriptRegistry)), "Add", jsxScript));

            method.Statements.Add(ifNotContains);


            // 6.) Create method reference to base "FrameworkInitialize" method.
            CodeMethodReferenceExpression frameworkInitializeMethod = new CodeMethodReferenceExpression(new CodeBaseReferenceExpression(), "FrameworkInitialize");
            method.Statements.Add(new CodeMethodInvokeExpression(frameworkInitializeMethod));


            return method;
        }


        protected virtual CodeMemberMethod GenerateBuildScriptMethod(JavaScriptCodeDom javascriptCodeDom)
        {
            // 1.) Create the CodeDOM method.
            CodeMemberMethod method = new CodeMemberMethod();

            // 2.) Create a CodeDOM reference to all of the object types to be used.
            CodeTypeReference stringBuilderType = new CodeTypeReference(typeof(System.Text.StringBuilder));
            CodeTypeReference expressionBuilderType = new CodeTypeReference(typeof(System.Web.Compilation.ExpressionBuilder));
            CodeTypeReference javascriptBuildProviderType = new CodeTypeReference(typeof(JavaScriptBuildProvider));
            CodeTypeReference stringType = new CodeTypeReference(typeof(System.String));

            method.Name = "BuildScript";
            method.Attributes = MemberAttributes.Public;
            method.ReturnType = stringBuilderType;
            
            // 3.) Create a "scriptBuilder" StringBuilder variable.
            CodeVariableDeclarationStatement scriptBuilderVariable = CreateObjectVariable(stringBuilderType, "scriptBuilder");
            method.Statements.Add(scriptBuilderVariable); // Add the statement to the method.

            // 4.) Create a CodeDOM reference to the "scriptBuilder" variable for later use.
            CodeVariableReferenceExpression scriptBuilderReference = new CodeVariableReferenceExpression("scriptBuilder");

            // 5.) Create a CodeDOM reference to the "Append" method of the StringBuilder class.
            CodeMethodReferenceExpression appendMethodReference = new CodeMethodReferenceExpression(scriptBuilderReference, "Append");

            // 6.) Loop through all of the SourceElements of the JavaScriptCodeDom object generated by the parser, 
            // building out the content of the "BuildScriptFile" method as it goes.
            foreach (List<JavaScriptSourceElement> sourceElementList in javascriptCodeDom.SourceElements)
            {
                if (sourceElementList == null) { continue; }

                foreach (JavaScriptSourceElement sourceElement in sourceElementList)
                {
                    CodeStatement newStatement = null;
                    if (sourceElement.Type == JavaScriptSourceElementType.EndOfFile) { break; }

                    switch (sourceElement.Type)
                    {
                        case JavaScriptSourceElementType.Literal:

                            newStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(appendMethodReference, new CodeSnippetExpression("\"" + sourceElement.SourceCode + "\"")));
                            break;

                        case JavaScriptSourceElementType.EvalExpression:
                        case JavaScriptSourceElementType.DataBindingExpression:

                            newStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(appendMethodReference, new CodeSnippetExpression(sourceElement.SourceCode)));
                            break;

                        case JavaScriptSourceElementType.SnippetExpression:

                            newStatement = new CodeSnippetStatement(sourceElement.SourceCode);
                            break;

                        case JavaScriptSourceElementType.CodeExpression:

                            newStatement = new CodeExpressionStatement(new CodeMethodInvokeExpression(appendMethodReference, CreateCodeExpressionCodeExpression(sourceElement.Prefix, sourceElement.SourceCode)));
                            break;

                        default:

                            continue;
                    }


                    newStatement.LinePragma = new CodeLinePragma(FileName, sourceElement.LineNumber);
                    method.Statements.Add(newStatement);
                }
            }

            method.Statements.Add(new CodeMethodReturnStatement(scriptBuilderReference));

            return method;
        }


        protected virtual CodeVariableDeclarationStatement CreateObjectVariable(CodeTypeReference variableType, string variableName)
        {
            CodeVariableDeclarationStatement variable = new CodeVariableDeclarationStatement(variableType, variableName);
            
            CodeObjectCreateExpression variableCreateExpression = new CodeObjectCreateExpression();
            variableCreateExpression.CreateType = variableType;
            
            variable.InitExpression = variableCreateExpression;

            return variable;
        }


        /*
        // Creates a CodeDOM representation of the VirtualPathType property that gets a Type object of the internal 
        // System.Web.VirtualPath type.
        protected virtual CodeMemberField CreateVirtualPathTypeField()
        {
            CodeMemberField field = new CodeMemberField();

            field.Name = "VirtualPathType";
            field.Type = new CodeTypeReference(typeof(System.Type));
            field.Attributes = MemberAttributes.Private | MemberAttributes.Static;

            field.InitExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(System.Type)), "GetType", new CodePrimitiveExpression("System.Web.VirtualPath, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), new CodePrimitiveExpression(true));

            return field;
        }


        protected virtual CodeMemberField CreateVirtualPathObjField()
        {
            CodeMemberField field = new CodeMemberField();

            field.Name = "_virtualPathObj";
            field.Type = new CodeTypeReference("object");
            field.Attributes = MemberAttributes.Private;

            field.InitExpression = new CodePrimitiveExpression(null);

            return field;
        }


        protected virtual CodeMemberProperty CreateVirtualPathObjProperty()
        {
            string generatedTypeName = DetermineReferencedTypeName();

            CodeMemberProperty property = new CodeMemberProperty();

            property.Name = "VirtualPathObj";
            property.Type = new CodeTypeReference("object");
            property.Attributes = MemberAttributes.Family; // protected
            property.HasGet = true;
            property.HasSet = false;

            CodeFieldReferenceExpression privateField = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "_virtualPathObj");
            CodeBinaryOperatorExpression privateFieldIsNull = new CodeBinaryOperatorExpression(privateField, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(null));

            // Build out the true statements of the conditional.
            CodeVariableDeclarationStatement methodInfoVariable = new CodeVariableDeclarationStatement(typeof(System.Reflection.MethodInfo), "createMethodInfo");
            methodInfoVariable.InitExpression = new CodeMethodInvokeExpression(
                new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "VirtualPathType"),
                "GetMethod",
                new CodePrimitiveExpression("Create"),
                new CodeBinaryOperatorExpression(new CodeSnippetExpression("System.Reflection.BindingFlags.Public"), CodeBinaryOperatorType.BitwiseOr, new CodeSnippetExpression("System.Reflection.BindingFlags.Static")),
                new CodePrimitiveExpression(null),
                new CodeArrayCreateExpression(typeof(System.Type), new CodeExpression[] { new CodeTypeOfExpression(typeof(System.String)) }),
                new CodePrimitiveExpression(null));

            CodeAssignStatement assignPrivateField = new CodeAssignStatement(
                new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "_createVirtualPath"),
                new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("createMethodInfo"), "Invoke"),
                    new CodeVariableReferenceExpression("createMethodInfo"), new CodePrimitiveExpression(base.VirtualPath)));


            // Build the conditional.
            CodeConditionStatement ifNull = new CodeConditionStatement();
            ifNull.Condition = privateFieldIsNull;
            ifNull.TrueStatements.Add(methodInfoVariable);
            ifNull.TrueStatements.Add(assignPrivateField);

            CodeMethodReturnStatement returnPrivateField = new CodeMethodReturnStatement(privateField);

            property.GetStatements.Add(ifNull);
            property.GetStatements.Add(returnPrivateField);

            return property;
        }


        // Creates a CodeDOM representation of the GetExpressionBuilderMethod delegate declaration.
        protected virtual CodeTypeDelegate CreateGetExpressionBuilderMethodDelegate()
        {
            CodeTypeDelegate delegateMethod = new CodeTypeDelegate("GetExpressionBuilderMethod");

            delegateMethod.ReturnType = new CodeTypeReference(typeof(System.Web.Compilation.ExpressionBuilder));
            delegateMethod.Attributes = MemberAttributes.Private;
            delegateMethod.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(System.String)), "prefix"));
            delegateMethod.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(System.Object)), "virtualPath"));

            return delegateMethod;
        }


        protected virtual CodeMemberField CreateGetExpressionBuilderField()
        {
            CodeMemberField field = new CodeMemberField();

            field.Name = "_getExpressionBuilder";
            field.Type = new CodeTypeReference("GetExpressionBuilderMethod");
            field.Attributes = MemberAttributes.Private | MemberAttributes.Static;

            field.InitExpression = new CodePrimitiveExpression(null);

            return field;
        }


        protected virtual CodeMemberProperty CreateGetExpressionBuilderProperty()
        {
            string generatedTypeName = DetermineReferencedTypeName();

            CodeMemberProperty property = new CodeMemberProperty();

            property.Name = "GetExpressionBuilder";
            property.Type = new CodeTypeReference("GetExpressionBuilderMethod"); // Refactor
            property.Attributes = MemberAttributes.Private | MemberAttributes.Static;
            property.HasGet = true;
            property.HasSet = false;

            CodeFieldReferenceExpression privateField = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "_getExpressionBuilder"); // Reference to static field.
            CodeBinaryOperatorExpression privateFieldIsNull = new CodeBinaryOperatorExpression(privateField, CodeBinaryOperatorType.ValueEquality, new CodePrimitiveExpression(null));

            // Build out the true statements of the conditional.
            CodeCommentStatement methodInfoComment = new CodeCommentStatement("Initialize and cache the dynamic method delegate to the ASP.NET internal static method \"GetExpressionBuilder\" of the System.Web.Compilation.ExpressionBuilder class.");
            CodeVariableDeclarationStatement methodInfoVariable = new CodeVariableDeclarationStatement(typeof(System.Reflection.MethodInfo), "getExpressionBuilderMethodInfo");
            methodInfoVariable.InitExpression = new CodeMethodInvokeExpression(
                new CodeTypeOfExpression(typeof(System.Web.Compilation.ExpressionBuilder)), // Refactor
                "GetMethod",
                new CodePrimitiveExpression("GetExpressionBuilder"),
                new CodeBinaryOperatorExpression(new CodeSnippetExpression("System.Reflection.BindingFlags.NonPublic"), CodeBinaryOperatorType.BitwiseOr, new CodeSnippetExpression("System.Reflection.BindingFlags.Static")),
                new CodePrimitiveExpression(null),
                new CodeArrayCreateExpression(typeof(System.Type), new CodeExpression[] { new CodeTypeOfExpression(typeof(System.String)), new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "VirtualPathType") }), // Refactor
                new CodePrimitiveExpression(null));
            CodeVariableDeclarationStatement dynamicMethodVariable = new CodeVariableDeclarationStatement(typeof(System.Reflection.Emit.DynamicMethod), "dynamicMethod");
            dynamicMethodVariable.InitExpression = new CodeObjectCreateExpression(
                typeof(System.Reflection.Emit.DynamicMethod),
                new CodePrimitiveExpression("GetExpressionBuilder"),
                new CodeTypeOfExpression(typeof(System.Web.Compilation.ExpressionBuilder)), // Refactor
                new CodeArrayCreateExpression(typeof(System.Type), new CodeExpression[] { new CodeTypeOfExpression(typeof(System.String)), new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "VirtualPathType") }), // Refactor
                new CodePrimitiveExpression(true));
            CodeVariableDeclarationStatement ilGeneratorVariable = new CodeVariableDeclarationStatement(typeof(System.Reflection.Emit.ILGenerator), "ilGenerator");
            ilGeneratorVariable.InitExpression = new CodeMethodInvokeExpression(
                new CodeVariableReferenceExpression("dynamicMethod"),
                "GetILGenerator");
            CodeMethodInvokeExpression pushParameter1 = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("ilGenerator"), "Emit", new CodeSnippetExpression("System.Reflection.Emit.OpCodes.Ldarg_0"));
            CodeMethodInvokeExpression pushParameter2 = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("ilGenerator"), "Emit", new CodeSnippetExpression("System.Reflection.Emit.OpCodes.Ldarg_1"));
            CodeMethodInvokeExpression callMethodInfo = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("ilGenerator"), "Emit", new CodeSnippetExpression("System.Reflection.Emit.OpCodes.Call"), new CodeVariableReferenceExpression("getExpressionBuilderMethodInfo"));
            CodeMethodInvokeExpression popAndReturn = new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("ilGenerator"), "Emit", new CodeSnippetExpression("System.Reflection.Emit.OpCodes.Ret"));
            CodeAssignStatement assignPrivateField = new CodeAssignStatement(
                new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(generatedTypeName), "_getExpressionBuilder"),
                new CodeCastExpression("GetExpressionBuilderMethod", new CodeMethodInvokeExpression(new CodeVariableReferenceExpression("dynamicMethod"), "CreateDelegate", new CodeTypeOfExpression("GetExpressionBuilderMethod"))));

            // Build the conditional.
            CodeConditionStatement ifNull = new CodeConditionStatement();
            ifNull.Condition = privateFieldIsNull;
            ifNull.TrueStatements.Add(methodInfoComment);
            ifNull.TrueStatements.Add(methodInfoVariable);
            ifNull.TrueStatements.Add(dynamicMethodVariable);
            ifNull.TrueStatements.Add(ilGeneratorVariable);
            ifNull.TrueStatements.Add(pushParameter1);
            ifNull.TrueStatements.Add(pushParameter2);
            ifNull.TrueStatements.Add(callMethodInfo);
            ifNull.TrueStatements.Add(popAndReturn);
            ifNull.TrueStatements.Add(assignPrivateField);

            CodeMethodReturnStatement returnPrivateField = new CodeMethodReturnStatement(privateField);

            property.GetStatements.Add(ifNull);
            property.GetStatements.Add(returnPrivateField);

            return property;
        }
        */


        protected virtual CodeExpression CreateCodeExpressionCodeExpression(string prefix, string expression)
        {
            // 1.) Create and initialize a ExpressionBuilderContext object.
            ExpressionBuilderContext builderContext = new ExpressionBuilderContext(base.VirtualPath);

            // 2.) Get the appropriate ExpressionBuilder object for the parsed expression.
            ExpressionBuilder builder = GetExpressionBuilder(prefix, VirtualPathObj);

            // 3.) Create and initialize a BoundPropertyEntry object.
            BoundPropertyEntry propertyEntry = CreateBoundPropertyEntry();
            propertyEntry.Expression = expression;
            propertyEntry.ExpressionBuilder = builder;
            propertyEntry.ExpressionPrefix = prefix;

            // 4.) Call the public "ParseExpression" method and retain the "parsedData" object it returns.
            object parsedData = builder.ParseExpression(expression, propertyEntry.GetType(), builderContext);

            // 5.) Call the "GetCodeExpression" method of the ExpressionBuilder object and return its value.
            return builder.GetCodeExpression(propertyEntry, parsedData, builderContext);
        }


        protected virtual void ProcessRenderMode(JavaScriptCodeDom codeDom, ScriptType scriptType, out CodeTypeDeclaration handlerClass)
        {
            handlerClass = null;

            switch (scriptType)
            {
                case ScriptType.Inline:



                    break;

                case ScriptType.File:

                    // In the contructor of JavaScriptBuildProvider, check for the RenderMode attribute value. If 
                    // RenderMode is either "File" or "Resource", programmatically inject an HTTP Handler mapped 
                    // to the ScriptName + ".js" file name and to the generated type. The type will then be made 
                    // to implement IHttpHandler and will server out the file when the file name is requested. The 
                    // script block generated will simply reference ScriptName + ".js" as the value of its "src" 
                    // attribute. 

                    // Lou, think of another way to do this so that ScriptResource.axd can grab these files, along 
                    // with any other HTTP Handler or HTTP Module that may want to intercept it.

                    handlerClass = CreateFileHttpHandlerClass(codeDom);

                    break;

                case ScriptType.Embedded:

                    handlerClass = CreateEmbeddedHttpHandlerClass(codeDom);

                    break;
            }
        }


        protected virtual CodeTypeDeclaration CreateFileHttpHandlerClass(JavaScriptCodeDom codeDom)
        {
            CodeTypeReference iHttpHandlerType = new CodeTypeReference(typeof(System.Web.IHttpHandler));


            CodeTypeDeclaration handlerClass = new CodeTypeDeclaration();

            handlerClass.Name = codeDom.ScriptName + "FileHandler";
            handlerClass.IsClass = true;
            handlerClass.IsPartial = true;
            handlerClass.TypeAttributes = TypeAttributes.Public;

            handlerClass.BaseTypes.Add(iHttpHandlerType);
            handlerClass.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.Runtime.CompilerServices.CompilerGlobalScopeAttribute))));


            // Create and add a CodeDOM representation of an implementation of the "IsReusable" property.
            CodeMemberProperty isReusableProperty = new CodeMemberProperty();

            isReusableProperty.Name = "IsReusable";
            isReusableProperty.Attributes = MemberAttributes.Public;
            isReusableProperty.HasGet = true;
            isReusableProperty.HasSet = false;
            isReusableProperty.Type = new CodeTypeReference(typeof(System.Boolean));
            isReusableProperty.ImplementationTypes.Add(iHttpHandlerType);

            isReusableProperty.GetStatements.Add(new CodeMethodReturnStatement(new CodePrimitiveExpression(true)));

            handlerClass.Members.Add(isReusableProperty);


            // Create and add a CodeDOM representation of an implementation of the "ProcessRequest" method.
            CodeMemberMethod processRequestMethod = new CodeMemberMethod();
            
            processRequestMethod.Name = "ProcessRequest";
            processRequestMethod.ReturnType = new CodeTypeReference(typeof(void));
            processRequestMethod.Attributes = MemberAttributes.Public;
            processRequestMethod.ImplementationTypes.Add(iHttpHandlerType);

            processRequestMethod.Parameters.Add(new CodeParameterDeclarationExpression(new CodeTypeReference(typeof(System.Web.HttpContext)), "context"));

            CodeTypeReference jsxScriptType = new CodeTypeReference(typeof(JsxScript));
            
            CodeVariableDeclarationStatement newJsxScript = new CodeVariableDeclarationStatement(jsxScriptType, "jsxScript");
            newJsxScript.InitExpression = new CodeMethodInvokeExpression(new CodeTypeReferenceExpression(typeof(JsxScriptRegistry)), "GetScript", new CodePrimitiveExpression(FileName));
            processRequestMethod.Statements.Add(newJsxScript);

            CodeVariableReferenceExpression jsxScript = new CodeVariableReferenceExpression("jsxScript");

            CodeBinaryOperatorExpression left = new CodeBinaryOperatorExpression(jsxScript, CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null));
            CodeBinaryOperatorExpression right = new CodeBinaryOperatorExpression(new CodePropertyReferenceExpression(jsxScript, "Instance"), CodeBinaryOperatorType.IdentityInequality, new CodePrimitiveExpression(null));

            CodeConditionStatement conditional = new CodeConditionStatement();
            conditional.Condition = new CodeBinaryOperatorExpression(left, CodeBinaryOperatorType.BooleanAnd, right);
            
            
            CodeVariableDeclarationStatement scriptContent = new CodeVariableDeclarationStatement(new CodeTypeReference(typeof(string)), "scriptContent");
            scriptContent.InitExpression = new CodeMethodInvokeExpression(new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(jsxScript, "Instance"), "BuildScript"), "ToString");
            conditional.TrueStatements.Add(scriptContent);

            conditional.TrueStatements.Add(new CodeMethodInvokeExpression(new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("context"), "Response"), "Write", new CodeVariableReferenceExpression("scriptContent")));

            processRequestMethod.Statements.Add(conditional);


            handlerClass.Members.Add(processRequestMethod);


            return handlerClass;
        }


        protected virtual CodeTypeDeclaration CreateEmbeddedHttpHandlerClass(JavaScriptCodeDom codeDom)
        {
            CodeTypeDeclaration handlerClass = new CodeTypeDeclaration();
            handlerClass.Name = codeDom.ScriptName + "EmbeddedHandler";

            handlerClass.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(System.Runtime.CompilerServices.CompilerGlobalScopeAttribute))));



            return handlerClass;
        }
        
        
        public virtual string DetermineReferencedTypeName()
        {
            int indexOfLastPeriod = Parser.ReferencedTypeName.LastIndexOf('.');

            if (indexOfLastPeriod != -1)
            {
                return Parser.ReferencedTypeName.Substring(indexOfLastPeriod + 1);
            }

            return Parser.ReferencedTypeName;
        }


        public virtual string DetermineReferencedTypeNamespace()
        {
            int indexOfLastPeriod = Parser.ReferencedTypeName.LastIndexOf('.');

            if (indexOfLastPeriod > 0)
            {
                return Parser.ReferencedTypeName.Substring(0, indexOfLastPeriod);
            }

            return String.Empty;
        }


        public override Type GetGeneratedType(CompilerResults results)
        {
            return results.CompiledAssembly.GetType(Parser.ReferencedTypeName);
        }
        //------\\ Methods //-----------------------------------------------//
    }
}